"""
@File    :   cli.py
@Time    :   2024/02/11 17:06:42
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   命令行汇总
"""

# 按需导入, 尽量不在全局进行导入, 防止耗时过长的导入导致typer-cli的补全变得卡和慢
import typer

from src.utils.execution_timer import execution_timer

# 主命令
cli = typer.Typer()
# rsa加解密相关命令
rsa_cli = typer.Typer()
# alembic数据库迁移相关命令
alembic_cli = typer.Typer()
# redis相关命令
redis_cli = typer.Typer()
# 密码hash及其验证相关命令
password_cli = typer.Typer()
# funboost任务队列相关命令
funboost_cli = typer.Typer()
# 应用相关命令
app_cli = typer.Typer()

# 添加子命令
cli.add_typer(rsa_cli, name="rsa")
cli.add_typer(alembic_cli, name="alembic")
cli.add_typer(password_cli, name="password")
cli.add_typer(funboost_cli, name="funboost")
cli.add_typer(redis_cli, name="redis")
cli.add_typer(app_cli, name="app")


@cli.command("test_set")
@execution_timer
def test_set(key: str, value: str = "", expiration_seconds: int = 1):
    import asyncio
    import random

    from asyncer import asyncify, syncify
    from redis.asyncio import Redis
    from sqlalchemy import and_, exists, func, insert, or_, select, update
    from sqlalchemy.exc import IntegrityError
    from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
    from sqlalchemy.orm import sessionmaker

    from src.business.admin.system.menu.models import Menu
    from src.business.admin.system.role.models import Role
    from src.business.admin.system.role.schemas.validate import RoleCreateValidateSchema
    from src.business.admin.system.role.service import RoleService
    from src.business.admin.system.tenant.package.models import TenantPackage
    from src.business.admin.system.tenant.package.service import TenantPackageService
    from src.business.admin.system.tenant.tenant.models import Tenant
    from src.business.admin.system.tenant.tenant.schemas.out import (
        TenantCompleteOutSchema,
    )
    from src.business.admin.system.tenant.tenant.service import TenantService
    from src.business.admin.system.user.models import User, UserRoleRel
    from src.business.admin.system.user.schemas.validate import UserCreateValidateSchema
    from src.business.admin.system.user.service import UserService
    from src.config import get_app_settings
    from src.enums.redis_db import RedisDbNumEnum
    from src.utils.auto_import import AutoImportUtil
    from src.utils.cashews import data_cache
    from src.utils.jinja2 import render_async_from_string
    from src.utils.path import PathUtil

    settings = get_app_settings()

    redis = Redis(
        host=settings.redis_host,
        password=settings.redis_password,
        port=settings.redis_port,
        db=RedisDbNumEnum.cache_db.value,
        decode_responses=True,
    )
    # 创建异步引擎对象
    async_engine = create_async_engine(
        settings.master_async_orm_url,
        echo=settings.orm_echo,
        future=settings.orm_future,
        pool_size=settings.orm_process_pool_size,
        pool_pre_ping=settings.orm_pool_pre_ping,
    )

    # 创建异步会话管理对象(连接池对象, 每次调用就会取出一个连接)
    masterSessionLocal = sessionmaker(
        async_engine,
        class_=AsyncSession,
        autocommit=settings.orm_auto_commit,
        autoflush=settings.orm_auto_flush,
        expire_on_commit=settings.orm_expire_on_commit,
    )
    session: AsyncSession = masterSessionLocal()

    @data_cache.transaction()
    async def test():
        template_string = "{{ a }}测试{% for i in b %}{{i}}{%endfor%}"
        context = {"b": [1, 2, 3, 4, 5]}
        result = await render_async_from_string(template_string, context=context)
        print(result)

    async def async_func():
        total_sql = select(func.count()).select_from(User).where(User.id == 200)
        async with session.begin():
            total_res = await session.execute(total_sql)
        print(total_res.scalar())

    def sync_func():
        syncify(async_func)()

    async def main():
        await asyncify(sync_func)()
        await redis.close()
        await session.close()
        # await data_cache.close()

    loop = asyncio.new_event_loop()
    loop.run_until_complete(main())


@app_cli.command("init")
@execution_timer
def app_init():
    """初始化整个应用, 构建初始的数据"""
    pass


@app_cli.command("reset")
@execution_timer
def app_reset():
    """重置整个应用
    1. 重建sql
    2. 重建redis
    3. 构建初始的数据
    """
    confirm = typer.confirm("该操作会清空并重建应用的所有数据, 是否继续?")
    if confirm:
        import asyncio

        from asyncer import asyncify, syncify

        from .utils.data_operate import DataInitOperate
        from .utils.orm_operate import OrmOperate
        from .utils.redis_operate import RedisOperate

        # 封装成异步函数
        async def async_func():
            typer.echo("正在重建应用")
            # 重建redis
            RedisOperate.flush_all_db()
            # 重建sql
            OrmOperate.alembic_drop_orm()
            OrmOperate.alembic_init_orm()
            # 初始化数据
            await DataInitOperate.data_init()
            # 构建初始的数据
            typer.echo("应用重建成功")

        def sync_func():
            syncify(async_func)()

        async def main():
            await asyncify(sync_func)()

        # 这样用不会导致aiomysql的RuntimeError
        loop = asyncio.new_event_loop()
        loop.run_until_complete(main())


@rsa_cli.command("generate-key")
@execution_timer
def generate_rsa_key():
    """生成rsa加解密的公钥与私钥文件"""

    from src.utils.rsa_encryption import RsaEncryptionUtil

    typer.echo("开始创建Rsa公钥与秘钥文件")
    RsaEncryptionUtil.generate_rsa()
    typer.echo("创建Rsa公钥与秘钥文件完成")


@rsa_cli.command("encrypt")
@execution_timer
def encrypt_str(string: str):
    """rsa加密

    Args:
        string (str): 需加密的文本
    """
    from src.utils.rsa_encryption import RsaEncryptionUtil

    result = RsaEncryptionUtil.rsa_encrypt(string, auto_decode=True)
    print(result)


@rsa_cli.command("decrypt")
@execution_timer
def decrypt_str(string: str):
    """rsa解密

    Args:
        string (str): 需解密的文本
    """
    from src.utils.rsa_encryption import RsaEncryptionUtil

    result = RsaEncryptionUtil.rsa_decrypt(string)
    print(result)


@alembic_cli.command("init-db")
@execution_timer
def alembic_init_db():
    """配合alembic进行数据库初始化"""
    typer.echo("正在初始化数据库")
    from cli.utils.orm_operate import OrmOperate

    OrmOperate.alembic_init_orm()
    typer.echo("初始化数据库完成")


@alembic_cli.command("drop-db")
@execution_timer
def alembic_drop_db():
    """配合alembic进行数据库清空与alembic表还原"""
    confirm = typer.confirm("该操作会清空数据库中所有内容, 是否继续?")
    if confirm:
        typer.echo("正在清空数据库")
        from cli.utils.orm_operate import OrmOperate

        OrmOperate.alembic_drop_orm()
        typer.echo("清空数据库成功")


@alembic_cli.command("revision")
@execution_timer
def alembic_revision(msg: str):
    """alembic生成迁移文件"""
    typer.echo("正在生成迁移文件")
    from cli.utils.orm_operate import OrmOperate

    OrmOperate.alembic_revision(msg)
    typer.echo("迁移文件生成完成")


@alembic_cli.command("upgrade")
@execution_timer
def alembic_upgrade(revision: str):
    """alembic迁移"""
    confirm = typer.confirm("该操作会修改数据库中字段信息, 是否继续?")
    if confirm:
        typer.echo("正在迁移")
        from cli.utils.orm_operate import OrmOperate

        OrmOperate.alembic_upgrade(revision)
        typer.echo("迁移完成")


@alembic_cli.command("downgrade")
@execution_timer
def alembic_downgrade(revision: str):
    """alembic回退"""
    typer.echo("正在回退")
    from cli.utils.orm_operate import OrmOperate

    OrmOperate.alembic_downgrade(revision)
    typer.echo("回退完成")


@redis_cli.command("flushdb")
@execution_timer
def redis_flushdb(db_num: int):
    """清空指定的redis db"""

    confirm = typer.confirm(f"该操作会清空redis的db_{db_num}的数据, 是否继续?")
    if confirm:
        typer.echo(f"正在清空db_{db_num}")
        from .utils.redis_operate import RedisOperate

        RedisOperate.flush_db(db_num)
        typer.echo(f"db_{db_num}清空完成")


@redis_cli.command("flushall")
@execution_timer
def redis_flushall():
    """清空redis的所有db"""

    confirm = typer.confirm("该操作会清空整个redis的数据, 是否继续?")
    if confirm:
        typer.echo("正在清空")
        from .utils.redis_operate import RedisOperate

        RedisOperate.flush_all_db()
        typer.echo("清空完成")


@password_cli.command("hash")
@execution_timer
def password_hash(password: str):
    """密码hash化

    Args:
        password (str): 密码明文
    """
    typer.echo("正在进行密码hash化")
    from src.utils.password_encryption import PswHashUtil

    hashed_password = PswHashUtil.hash_psw(password)
    print(hashed_password)


@password_cli.command("verify")
@execution_timer
def password_hash_verify(plain_password: str, hashed_password: str):
    """验证密码明文与hash

    Args:
        plain_password (str): 明文密码
        hashed_password (str): 密码hash
    """
    typer.echo("正在验证密码明文与hash")
    from src.utils.password_encryption import PswHashUtil

    result = PswHashUtil.verify_psw(plain_password, hashed_password)
    print(result)


@funboost_cli.command("start")
def funboost_start():
    """启动funboost任务队列消费者"""
    import time

    from funboost.timing_job.apscheduler_use_redis_store import (
        funboost_background_scheduler_redis_store,
    )

    from src.business.admin.system.oauth2.token.boost_task import (
        create_double_token_clean_user_logged_tokens_zset_handler,
        delete_double_tokens_clean_user_logged_token_zset_handler,
        delete_expired_double_tokens_timed_handler,
        refresh_access_token_clean_user_logged_token_zset_handler,
    )
    from src.config import get_app_settings

    # 获取应用配置
    settings = get_app_settings()

    # 启动异步任务消费者
    # 登录(创建双Tokne)时清理用户的已登录双token的zset的异步任务
    create_double_token_clean_user_logged_tokens_zset_handler.multi_process_consume(2)
    # 使用刷新令牌刷新访问令牌时清理用户的已登录双token的zset的异步任务
    refresh_access_token_clean_user_logged_token_zset_handler.multi_process_consume(2)
    # 删除双Token时清理用户的已登录双token的zset的异步任务
    delete_double_tokens_clean_user_logged_token_zset_handler.multi_process_consume(2)

    # 启动定时任务消费者
    # 开启定时任务(paused=False表示直接执行paused=True则表示需要增删改查定时任务不直接执行)
    funboost_background_scheduler_redis_store.start(paused=False)
    # 添加定时任务
    # 定时删除过期的双Token(单独一个进程运行)
    delete_expired_double_tokens_timed_handler.multi_process_consume(1)
    funboost_background_scheduler_redis_store.add_push_job(
        delete_expired_double_tokens_timed_handler,
        "interval",
        id="1",
        name="delete_expired_double_tokens",
        seconds=settings.default_delete_expired_double_tokens_seconds,
        max_instances=1,
        replace_existing=True,
    )
    # 定时创建验证码
    if settings.enable_user_login_captcha:
        from src.business.admin.system.oauth2.auth.captcha.enums import CaptchaTypeEnum

        if settings.user_login_captcha_type is CaptchaTypeEnum.image:
            from src.business.admin.system.oauth2.auth.captcha.boost_task import (
                create_image_captcha_timed_handler,
            )

            # 定时创建图形验证码
            create_image_captcha_timed_handler.multi_process_consume(1)
            funboost_background_scheduler_redis_store.add_push_job(
                create_image_captcha_timed_handler,
                "interval",
                id="2",
                name="create_image_captcha",
                seconds=settings.default_create_image_captcha_seconds,
                max_instances=1,
                replace_existing=True,
            )
        elif settings.user_login_captcha_type is CaptchaTypeEnum.sliding_puzzle:
            from src.business.admin.system.oauth2.auth.captcha.boost_task import (
                create_sliding_captcha_timed_handler,
            )

            # 定时创建滑块验证码
            create_sliding_captcha_timed_handler.multi_process_consume(1)
            funboost_background_scheduler_redis_store.add_push_job(
                create_sliding_captcha_timed_handler,
                "interval",
                id="3",
                name="create_sliding_captcha",
                seconds=settings.default_create_sliding_captcha_seconds,
                max_instances=1,
                replace_existing=True,
            )

    # python3.9以上版本需要保证主线程不退出
    while 1:
        time.sleep(100)


if __name__ == "__main__":
    cli()
